# This file is part of Buildbot.  Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members

import requests

from urllib import urlencode
from urlparse import parse_qs
from buildbot.util import json
from buildbot.www import auth
from buildbot.www import resource
from posixpath import join
from twisted.internet import defer
from twisted.internet import threads


class OAuth2LoginResource(auth.LoginResource):
    # disable reconfigResource calls
    needsReconfig = False

    def __init__(self, master, _auth):
        auth.LoginResource.__init__(self, master)
        self.auth = _auth

    def render_POST(self, request):
        return self.asyncRenderHelper(request, self.renderLogin)

    @defer.inlineCallbacks
    def renderLogin(self, request):
        code = request.args.get("code", [""])[0]
        if not code:
            url = yield self.auth.getLoginURL()
            defer.returnValue(url)
        else:
            details = yield self.auth.verifyCode(code)
            request.getSession().user_info = details
            raise resource.Redirect(self.auth.homeUri)


class OAuth2Auth(auth.AuthBase):
    name = 'oauth2'
    getTokenUseAuthHeaders = False
    authUri = None
    tokenUri = None
    grantType = 'authorization_code'
    authUriAdditionalParams = {}
    tokenUriAdditionalParams = {}
    loginUri = None
    homeUri = None
    sslVerify = None

    def __init__(self,
                 clientId, clientSecret, autologin=False, **kwargs):
        auth.AuthBase.__init__(self, **kwargs)
        self.clientId = clientId
        self.clientSecret = clientSecret
        self.autologin = autologin

    def reconfigAuth(self, master, new_config):
        self.master = master
        self.loginUri = join(new_config.www['url'], "auth/login")
        self.homeUri = new_config.www['url']

    def getConfigDict(self):
        return dict(name=self.name,
                    oauth2=True,
                    fa_icon=self.faIcon,
                    autologin=self.autologin
                    )

    def getLoginResource(self):
        return OAuth2LoginResource(self.master, self)

    def getLoginURL(self):
        """
        Returns the url to redirect the user to for user consent
        """
        oauth_params = {'redirect_uri': self.loginUri, 'client_id': self.clientId,
                        'response_type': 'code'}
        oauth_params.update(self.authUriAdditionalParams)
        return defer.succeed("%s?%s" % (self.authUri, urlencode(oauth_params)))

    def createSessionFromToken(self, token):
        s = requests.Session()
        s.params = {'access_token': token['access_token']}
        return s

    def get(self, session, path):
        ret = session.get(self.resourceEndpoint + path)
        return ret.json()

    # based on https://github.com/maraujop/requests-oauth
    # from Miguel Araujo, augmented to support header based clientSecret passing
    def verifyCode(self, code):
        def thd():  # everything in deferToThread is not counted with trial  --coverage :-(
            url = self.tokenUri
            data = {'redirect_uri': self.loginUri, 'code': code,
                    'grant_type': self.grantType}
            auth = None
            if self.getTokenUseAuthHeaders:
                auth = (self.clientId, self.clientSecret)
            else:
                data.update({'client_id': self.clientId, 'client_secret': self.clientSecret})
            data.update(self.tokenUriAdditionalParams)
            response = requests.post(url, data=data, auth=auth, verify=self.sslVerify)
            response.raise_for_status()
            if isinstance(response.content, basestring):
                try:
                    content = json.loads(response.content)
                except ValueError:
                    content = parse_qs(response.content)
                    for k, v in content.items():
                        content[k] = v[0]
            else:
                content = response.content

            session = self.createSessionFromToken(content)
            return self.getUserInfoFromOAuthClient(session)
        return threads.deferToThread(thd)

    def getUserInfoFromOAuthClient(self, c):
        return {}


class GoogleAuth(OAuth2Auth):
    name = "Google"
    faIcon = "fa-google-plus"
    resourceEndpoint = "https://www.googleapis.com/oauth2/v1"
    authUri = 'https://accounts.google.com/o/oauth2/auth'
    tokenUri = 'https://accounts.google.com/o/oauth2/token'
    authUriAdditionalParams = dict(scope=" ".join([
                                   'https://www.googleapis.com/auth/userinfo.email',
                                   'https://www.googleapis.com/auth/userinfo.profile',
                                   ]),
                                   access_type='offline')

    def getUserInfoFromOAuthClient(self, c):
        data = self.get(c, '/userinfo')
        return dict(full_name=data["name"],
                    username=data['email'].split("@")[0],
                    email=data["email"],
                    avatar_url=data["picture"])


class GitHubAuth(OAuth2Auth):
    name = "GitHub"
    faIcon = "fa-github"
    authUri = 'https://github.com/login/oauth/authorize'
    tokenUri = 'https://github.com/login/oauth/access_token'
    resourceEndpoint = 'https://api.github.com'

    def getUserInfoFromOAuthClient(self, c):
        user = self.get(c, '/user')
        orgs = self.get(c, join('/users', user['login'], "orgs"))
        return dict(full_name=user['name'],
                    email=user['email'],
                    username=user['login'],
                    groups=[org['login'] for org in orgs])
